#include "ParseParam.h"

#include <map>
#include <set>
#include <cmath>
#include <sstream>
#include <iomanip>
#include <iostream>
#include <unistd.h>
#include <limits.h>
#include "DumpLog.h"

namespace Parse
{
bool strIsNumber(const std::string &s)
{
    //1.
    if (s.empty())
    {
        return false;
    }

    //2.
    for (auto c : s)
    {
        if (c < '0' || c > '9')
        {
            return false;
        }
    }

    //3.
    return true;
}

bool tryToInt64(const std::string &s, int64_t &ret, const int64_t defaultValue, const int64_t minValue, const int64_t maxValue, const std::string &errorNote = "")
{
    //1.
    ret = defaultValue;
    
    //2.
    if (s.size() > 19 || s.empty())
    {
        if (errorNote.size())
        {
            LogError(errorNote);
        }

        return false;
    }

    //3.
    bool neg(false);
    std::string v(s);
    if (s[0] == '-')
    {
        neg = true;
        v   = s.substr(1, s.size() - 1);
    }

    //4.
    if (!strIsNumber(v))
    {
        if (errorNote.size())
        {
            LogError(errorNote);
        }

        return false;
    }

    //5.
    ret = std::stoll(v);
    if (neg)
    {
        ret = -ret;
    }

    //6.
    if (ret < minValue || ret > maxValue)
    {
        if (errorNote.size())
        {
            LogError(errorNote);
        }

        return false;
    }

    //7.
    return true;
}

bool tryToDouble(const std::string &s, long double &ret, const long double defaultValue, const long double minValue, const long double maxValue, const std::string &errorNote)
{
    //1.
    ret = defaultValue;

    //2.
    if (s.empty() || s.size() > 50)
    {
        if (errorNote.size())
        {
            LogError(errorNote);
        }

        return false;
    }

    //3.
    std::string a("1"), b("0");
    std::size_t i = s.find('e');
    if (i == std::string::npos)
    {
        i = s.find('E');
    }

    if (i == std::string::npos || i + 1 == s.size())
    {
        a = s;
    }
    else
    {
        if (i)
        {
            a = s.substr(0, i);
            ++i;
            if (s[i] == '+')
            {
                ++i;
                if (i == s.size())
                {
                    if (errorNote.size())
                    {
                        LogError(errorNote);
                    }

                    return false;
                }
            }

            b = s.substr(i, s.size() - i);
        }
        else
        {
            b = s;
        }
    }

    //4.
    int64_t bn(0);
    if (!tryToInt64(b, bn, 0, -300, 300))
    {
        if (errorNote.size())
        {
            LogError(errorNote);
        }

        return false;
    }

    //5.
    long double an(1);
    if (!a.empty())
    {
        bool neg = (a[0] == '-');
        if (neg)
        {
            a = a.substr(1, a.size() - 1);
        }

        if (a.empty())
        {
            if (errorNote.size())
            {
                LogError(errorNote);
            }

            return false;
        }

        i = a.find('.');
        if (i != std::string::npos)
        {
            if (i == 0 || i + 1 == a.size())
            {
                if (errorNote.size())
                {
                    LogError(errorNote);
                }

                return false;
            }

            if (!strIsNumber(a.substr(0, i)) || !strIsNumber(a.substr(i+1, a.size() - i - 1)))
            {
                if (errorNote.size())
                {
                    LogError(errorNote);
                }

                return false;
            }
        }
        else if(!strIsNumber(a))
        {
            if (errorNote.size())
            {
                LogError(errorNote);
            }

            return false;
        }

        an = std::stold(a);
        if (neg)
        {
            an = -an;
        }
    }

    if (bn)
    {
        an *= std::pow(10, bn);
    }

    //6.
    if (an < minValue || an > maxValue)
    {
        if (errorNote.size())
        {
            LogError(errorNote);
        }

        return false;
    }

    //7.

    ret = an;
    return true;
}

std::string getExeName()
{
    std::string name;
    name.resize(PATH_MAX);
    int32_t len = readlink("/proc/self/exe", name.data(), PATH_MAX - 1);
    if (len != -1) 
    {
        std::size_t i = name.rfind('/', len - 1);
        if (i != std::string::npos)
        {
            ++i;        
        }
        else
        {
            i = 0;
        }

        return name.substr(i, len - i);
    } 
    else 
    {
        return "";
    }
}

bool parseParam(char **params, int paramNum, const vecParseParam_t &vecParam)
{
    //1.
    std::size_t num(0);
    std::map<std::string, const ParseParam_t*> mapParam;
    for (auto &it : vecParam)
    {
        //1.1
        if (it.key.empty())
        {
            LogError("命令行参数[{}]的key不能为空", it.name);
            return false;
        }

        //1.2
        mapParam.emplace(it.key, &it);
        if (num == mapParam.size())
        {
            LogError("命令行参数[{}]的key={}已经存在", it.name, it.key);
            return false;
        }

        //1.3
        num = mapParam.size();
    }

    //2.
    std::set<const ParseParam_t*> setKey;
    if (paramNum > 2)
    {
        int64_t nv;
        long double lv;
    
        for (int i = 1; i + 1 < paramNum; ++i)
        {
            //2.1
            std::string key(params[i]);
            if (key.empty())
            {
                continue;
            }

            //2.2
            if (key[0] != '-' || key.size() == 1)
            {
                continue;
            }

            //2.3
            key = key.substr(1, key.size() - 1);
            auto it = mapParam.find(key);
            if (it == mapParam.end())
            {
                continue;
            }

            //2.4
            ++i;
            setKey.insert(it->second);
            const std::string note("参数[- " + it->first +"]获取失败");
            switch (it->second->type)
            {
            case ParamType_E::String_E:
                *static_cast<std::string*>(it->second->value) = params[i];
                break;

            case ParamType_E::Int32_E:
                if (!tryToInt64(params[i], nv, 0, std::numeric_limits<int32_t>::min(), std::numeric_limits<int32_t>::max(), note))
                {
                    return false;
                }

                *static_cast<int32_t*>(it->second->value) = nv;
                break;

            case ParamType_E::Double_E:
                if (!tryToDouble(params[i], lv, 0, std::numeric_limits<double>::min(), std::numeric_limits<double>::max(), note))
                {
                    return false;
                }

                *static_cast<double*>(it->second->value) = lv;
                break;

            case ParamType_E::Float_E:
                if (!tryToDouble(params[i], lv, 0, std::numeric_limits<float>::min(), std::numeric_limits<float>::max(), note))
                {
                    return false;
                }

                *static_cast<float*>(it->second->value) = lv;
                break;

            case ParamType_E::LDouble_E:
                if (!tryToDouble(params[i], lv, 0, std::numeric_limits<long double>::min(), std::numeric_limits<long double>::max(), note))
                {
                    return false;
                }

                *static_cast<long double*>(it->second->value) = lv;
                break;

            case ParamType_E::Int8_E:
                if (!tryToInt64(params[i], nv, 0, std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max(), note))
                {
                    return false;
                }

                *static_cast<int8_t*>(it->second->value) = nv;
                break;

            case ParamType_E::Int16_E:
                if (!tryToInt64(params[i], nv, 0, std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max(), note))
                {
                    return false;
                }

                *static_cast<int16_t*>(it->second->value) = nv;
                break;

            case ParamType_E::Int64_E:
                if (!tryToInt64(params[i], nv, 0, std::numeric_limits<int64_t>::min(), std::numeric_limits<int64_t>::max(), note))
                {
                    return false;
                }

                *static_cast<int64_t*>(it->second->value) = nv;
                break;
            
            case ParamType_E::UInt8_E:
                if (!tryToInt64(params[i], nv, 0, std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max(), note))
                {
                    return false;
                }

                *static_cast<uint8_t*>(it->second->value) = nv;
                break;

            case ParamType_E::UInt16_E:
                if (!tryToInt64(params[i], nv, 0, std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max(), note))
                {
                    return false;
                }

                *static_cast<uint16_t*>(it->second->value) = nv;
                break;

            case ParamType_E::UInt32_E:
                if (!tryToInt64(params[i], nv, 0, std::numeric_limits<uint32_t>::min(), std::numeric_limits<uint32_t>::max(), note))
                {
                    return false;
                }

                *static_cast<uint32_t*>(it->second->value) = nv;
                break;
            
            case ParamType_E::UInt64_E:
                if (!tryToInt64(params[i], nv, 0, std::numeric_limits<uint64_t>::min(), std::numeric_limits<uint64_t>::max(), note))
                {
                    return false;
                }

                *static_cast<uint64_t*>(it->second->value) = nv;
                break;

            default:
                continue;
            }
        }
    }

    //3.
    for (auto &it : vecParam)
    {
        if (!it.need || setKey.count(&it))
        {
            continue;
        }

        LogError("没有设置参数[-{}]参数", it.key);
        return false;
    }

    //4.
    return true;
}

inline void appendSpace(std::size_t n)
{
    while (n)
    {
        --n;
        std::cout << " ";
    }
}

void printCmd(const vecParseParam_t &vecParam, const std::string &tip)
{
    //1.
    std::map<ParamType_E, std::string> mapStr
    {
        {ParamType_E::Double_E, "double"},
        {ParamType_E::Float_E, "float"},
        {ParamType_E::String_E, "string"},
        {ParamType_E::Int32_E, "int32"},
        {ParamType_E::Int64_E, "int64"},
        {ParamType_E::Int16_E, "int16"},
        {ParamType_E::Int8_E, "int8"},
        {ParamType_E::UInt32_E, "uint32"},
        {ParamType_E::UInt64_E, "uint64"},
        {ParamType_E::UInt16_E, "uint16"},
        {ParamType_E::UInt8_E, "uint8"},
        {ParamType_E::LDouble_E, "long double"},
    };

    std::string colorRed, colorGreen, colorYellow, colorReset, typeStr;
    std::size_t nameSize(0), keySize(0), noteSize(86), typeSize(0), n;
    Log::DumpLog::getGlobalLog().getCoutColor(colorRed, colorGreen, colorYellow, colorReset);
    for (auto &param : vecParam)
    {
        //1.1
        if (param.name.size() > nameSize)
        {
            nameSize = param.name.size();
        }

        //1.2
        if (param.key.size() > keySize)
        {
            keySize = param.key.size();
        }

        //1.3
        n = param.note.find('\n');
        if (n == std::string::npos)
        {
            n = param.note.size();
        }

        if (n > noteSize)
        {
            noteSize = n;
        }

        //1.4
        typeStr = mapStr[param.type];
        if (typeStr.size() > typeSize)
        {
            typeSize = typeStr.size();
        }
    }

    //2.
    std::string splitLine;
    splitLine.resize(nameSize + keySize + noteSize + typeSize + 6);
    std::fill(splitLine.begin(), splitLine.end(), '-');
    splitLine.push_back('\n');

    //3.
    std::cout << splitLine << "功能: " << colorRed << tip << colorReset << "\n" << splitLine;

    //4.
    {
        std::string skey("");
        std::string line = "./" + getExeName() + " ";
        std::size_t lNum = line.size();
        for (auto &param : vecParam)
        {
            if (param.need)
            {
                skey = "-" + param.key + " " + param.name;
            }
            else
            {
                skey = "[-" + param.key + " " + param.name + "]";
            }

            if (line.size() + skey.size() > splitLine.size())
            {
                std::cout << line << "\n";
                line.resize(lNum);
                std::fill(line.begin(), line.end(), ' ');
            }

            line += skey + " ";
        }

        if (line.size())
        {
            std::cout << line;
        }
    }
    std::cout << "\n" << splitLine;

    //5.
    for (auto &param : vecParam)
    {
        //5.1
        std::cout << param.name;
        appendSpace(nameSize - param.name.size());
        
        //5.2
        std::cout << ": -" << param.key;
        appendSpace(keySize - param.key.size());

        //5.3
        typeStr = mapStr[param.type];
        std::cout << ", " << typeStr;
        appendSpace(typeSize - typeStr.size());

        //5.4
        std::cout << ", [";
        if (param.need)
        {
            std::cout << colorRed << "必须";
        }
        else
        {
            std::cout << colorGreen << "可选";
        }

        std::cout << colorReset << "], " << param.note << "\n";
    }

    //6.
    std::cout << splitLine;
}

}